# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright (c) 2008 - 2009  Simon Goodall <simon@simongoodall.co.uk>

## 
## yum-avahi-plugin is a yum plugin to use avahi to search for a local proxy
## server. This will allow roaming machines to take advantage of a proxy server
## when setup up on different networks. If no proxy servers are found, then yum
## will fall-back to it's default settings.

## This plugin searches for services registered as _yum_proxy._tcp. If the
## services contain a 'url' key in the txt section, then this is the url used
## for the proxy. If the 'url' key is missing, a url is constructed from the
## service record.

## Services can be registered programatically, or by using 
## avahi-publish-service as follows;
##
## > avahi-publish-service YumProxy _yum_proxy._tcp 3128 
##
## to use the current machine machine with a proxy on port 3128, or
##
## > avahi-publish-service YumProxy _yum_proxy._tcp 8080 url=http://host:8080/
##
## to specify an alternative proxy url instead.

## Notes
## Selinux will block yum from querying the avahi server.

import time
from yum import config
from yum.plugins import PluginYumExit, TYPE_CORE, TYPE_INTERACTIVE

import dbus, dbus.glib , dbus.exceptions, avahi, time, gobject
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop( set_as_default=True )

requires_api_version = '2.3'
plugin_type = (TYPE_CORE)

## Avahi service type
## TODO: Make this a config option.
type = '_yum_proxy._tcp'

## Map of discovered proxy servers
proxies = {}

def service_add(interface, protocol, name, type, domain, flags):

        ## Find out more info
        obj_path = server.ServiceResolverNew(interface, protocol, name, type, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0))

        resolver = dbus.Interface(bus.get_object(avahi.DBUS_NAME, obj_path),
                   avahi.DBUS_INTERFACE_SERVICE_RESOLVER)

        ## Hook up callbacks to 'Found' 
        resolver.connect_to_signal('Found', found)

def found(interface, protcol, name, type, domain, host, aprotocol, address, port, txt, flags):

	## Construct a proxy url from address and port number.
	## Use '[' ']' incase of IPv6 addresses.
	proxies[name] = "".join([ "http://", "[" , address, "]", ":", str(port)])

	## Look for a "url" txt field to override the one constructed above.
	txt_arr = avahi.txt_array_to_string_array(txt)
	for rec in txt_arr:
		(key, value) = rec.split("=")
		if key == "url":
			proxies[name] = value

	conduit.info(2, "Found proxy: " + proxies[name])

def postconfig_hook(conduit):
	conduit.info(2, "Searching for local proxies")

	try:

		global main_loop
		main_loop = gobject.MainLoop()

		global bus
		bus = dbus.SystemBus()

		global server
		## Get handle on avahi server
		server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
	
		## Find services from any interface and protocol
		interface =  avahi.IF_UNSPEC
		protocol = avahi.PROTO_UNSPEC
	
		## Use local domain.
		domain = "local"
	
		## Call method to create new browser, and get back an object 
		## path for it.
		obj_path = server.ServiceBrowserNew(interface, protocol, type, domain, dbus.UInt32(0))
	
		## Create browser interface for the new object
		browser = dbus.Interface(bus.get_object(avahi.DBUS_NAME, obj_path), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
	
		## Hook up signal for new found services
		browser.connect_to_signal('ItemNew', service_add)
	
		## Get main loop context so we can step rather than loop forever
	        context = main_loop.get_context()
	
		## We have issued our requests, however we do not always have an
		## immediate answer. This means context.pending() can return 
		## false early on. Here we sleep a little in the hope that some 
		## data will be ready in that time.

		## TODO: Is there a more reliable way to do this?
		time.sleep(0.5)
	        context.iteration(True)

		## Loop while there is still pending updates.
	        while (context.pending()):
	                context.iteration(True)
	
		## Pick the first proxy server discovered
		found_proxy = False
		for (key, value) in proxies.iteritems():
			## Is this the correct way to specify a URL option?
			config.YumConf.proxy = value # config.UrlOption(value)
			found_proxy = True
			break

		##  Display result	
		if found_proxy:
			conduit.info(2, "Using proxy: " + config.YumConf.proxy)
		else:
			conduit.info(2, "No proxy found using avahi")

	## selinunx can cause a DBusException, catch it so yum can continue
	except dbus.exceptions.DBusException, e:
		conduit.error(2, e)
